#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import asyncore
import logging
import socket
import struct
import threading
import time
import uuid
from asyncore import dispatcher

import schedule

from net.msg import EventType
from net.msg import MsgCtx
from net.msg import NetAdapter
from net.utils import utf8_decode
from net.utils import utf8_encode

logging.getLogger().setLevel(logging.INFO)


class ServHandler(dispatcher):

    def __init__(self, session_id, sock, serv, map=None, adapter=NetAdapter()):
        dispatcher.__init__(self, sock=sock, map=map)
        self.session_id = session_id
        self.adapter = adapter
        self.serv = serv

    def handle_read(self):
        raw_msglen = super(ServHandler, self).recv(4)
        if not raw_msglen:
            return None
        msglen = struct.unpack('>I', raw_msglen)[0]
        data = utf8_decode(super(ServHandler, self).recv(msglen))
        self.adapter.handle_data(handler=self, data=data)

    def handle_close(self):
        logging.info('connection close, addr=%s', self.addr)
        self.close()
        self.serv.close_handler(session_id=self.session_id)

    def handle_connect(self):
        logging.info('connect success, addr=%s', self.addr)

    def send(self, data):
        if data is not None:
            bin_msg = utf8_encode(data)
            msg = struct.pack('>I', len(bin_msg)) + bin_msg
            super(ServHandler, self).send(msg)

    def send_cmd(self, cmd=EventType.DATA, data=None):
        json_data = MsgCtx(cmd=cmd, data=data).to_json()
        self.send(data=json_data)


class NetServer(dispatcher):

    def __init__(self, host='localhost', port=10110, adapter=NetAdapter()):
        dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)
        self.adapter = adapter
        self.handler_table = dict()
        schedule.every(30).seconds.do(self.health_check)
        threading.Thread(target=self._start_timer, daemon=True).start()
        logging.info('Server socket start with binding %s port, Waiting for connections...', port)

    def _start_timer(self):
        while True:
            schedule.run_pending()
            time.sleep(1)

    def start(self):
        asyncore.loop()

    def handle_accept(self):
        pair = self.accept()
        if pair is not None:
            session_id = str(uuid.uuid4())
            sock, addr = pair
            logging.info('Incoming connection from %s with session_id=%s', repr(addr), session_id)
            _handler = ServHandler(session_id=session_id, sock=sock, serv=self, adapter=self.adapter)
            _handler.send_cmd(cmd=EventType.INIT, data=session_id)
            self.handler_table[session_id] = _handler

    def health_check(self):
        for _id, _handler in self.handler_table.items():
            if not _handler.connected:
                self.close_handler(session_id=_id)

    def close_handler(self, session_id):
        _handler = self.handler_table.pop(session_id)
        logging.info('remove handler %s from table', _handler.addr)

    def send_by_session(self, session_id, cmd=EventType.DATA, data=None):
        _handler = self.handler_table.get(session_id)
        if _handler is None:
            logging.warning('handler of session_id=%s is not exist in table', session_id)
            return
        _handler.send_cmd(cmd=cmd, data=data)


class EchoAdapter(NetAdapter):
    def __init__(self):
        pass

    def handle_data(self, handler, data):
        logging.debug(data)
        ctx = MsgCtx.ctx_from_json(data)
        if ctx.cmd == EventType.DATA:
            self.echo(handler=handler, data=ctx.data)

    def echo(self, handler, data):
        handler.send_cmd(cmd=EventType.DATA, data='Hello {}'.format(data))


if __name__ == '__main__':
    server = NetServer(adapter=EchoAdapter())
    server.start()
